/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ECMASCRIPT_INTERNAL_CALL_PARAMS_H
#define ECMASCRIPT_INTERNAL_CALL_PARAMS_H

#include "plugins/ecmascript/runtime/ecma_runtime_call_info.h"
#include "plugins/ecmascript/runtime/js_tagged_value.h"
#include "plugins/ecmascript/runtime/js_handle.h"
#include "plugins/ecmascript/runtime/js_function.h"

namespace ark::ecmascript {
class InternalCallParams {
public:
    static constexpr uint8_t RESERVE_INTERNAL_CALL_PARAMS_FIXED_LENGTH = 128;

    InternalCallParams() = default;

    ~InternalCallParams() = default;

    inline const JSTaggedType *GetArgv() const
    {
        if (IsFixedMode()) {
            return &fixedData_.front();
        }

        ASSERT_PRINT(variableLength_ > RESERVE_INTERNAL_CALL_PARAMS_FIXED_LENGTH, "internal call params mode error");
        return variableData_.data();
    }

    inline uint32_t GetLength() const
    {
        if (IsFixedMode()) {
            return fixedLength_;
        }

        ASSERT_PRINT(variableLength_ > RESERVE_INTERNAL_CALL_PARAMS_FIXED_LENGTH, "internal call params mode error");
        return variableLength_;
    }

    template <typename T>
    inline void MakeArgv(const JSHandle<T> &arg)
    {
        EnableFixedModeAndSetLength(1);
        fixedData_[0] = arg.GetTaggedType();
    }

    template <typename T0, typename T1>
    inline void MakeArgv(const JSHandle<T0> &arg0, const JSHandle<T1> &arg1)
    {
        EnableFixedModeAndSetLength(2);
        fixedData_[0] = arg0.GetTaggedType();
        fixedData_[1] = arg1.GetTaggedType();
    }

    template <typename T0, typename T1, typename T2>
    inline void MakeArgv(const JSHandle<T0> &arg0, const JSHandle<T1> &arg1, const JSHandle<T2> &arg2)
    {
        EnableFixedModeAndSetLength(3);
        fixedData_[0] = arg0.GetTaggedType();
        fixedData_[1] = arg1.GetTaggedType();
        fixedData_[2] = arg2.GetTaggedType();
    }

    template <typename T0, typename T1, typename T2, typename T3>
    inline void MakeArgv(const JSHandle<T0> &arg0, const JSHandle<T1> &arg1, const JSHandle<T2> &arg2,
                         const JSHandle<T3> &arg3)
    {
        EnableFixedModeAndSetLength(4);
        fixedData_[0] = arg0.GetTaggedType();
        fixedData_[1] = arg1.GetTaggedType();
        fixedData_[2] = arg2.GetTaggedType();
        fixedData_[3] = arg3.GetTaggedType();
    }

    inline void MakeEmptyArgv()
    {
        EnableFixedModeAndSetLength(0);
    }

    inline void MakeArgv(const JSTaggedValue arg)
    {
        EnableFixedModeAndSetLength(1);
        fixedData_[0] = arg.GetRawData();
    }

    inline void MakeArgv(const JSTaggedValue arg0, const JSTaggedValue arg1)
    {
        EnableFixedModeAndSetLength(2);
        fixedData_[0] = arg0.GetRawData();
        fixedData_[1] = arg1.GetRawData();
    }

    inline void MakeArgv(const JSTaggedValue arg0, const JSTaggedValue arg1, const JSTaggedValue arg2)
    {
        EnableFixedModeAndSetLength(3);
        fixedData_[0] = arg0.GetRawData();
        fixedData_[1] = arg1.GetRawData();
        fixedData_[2] = arg2.GetRawData();
    }

    inline void MakeArgv(const JSTaggedValue arg0, const JSTaggedValue arg1, const JSTaggedValue arg2,
                         const JSTaggedValue arg3)
    {
        EnableFixedModeAndSetLength(4);
        fixedData_[0] = arg0.GetRawData();
        fixedData_[1] = arg1.GetRawData();
        fixedData_[2] = arg2.GetRawData();
        fixedData_[3] = arg3.GetRawData();
    }

    void MakeArgv(const EcmaRuntimeCallInfo *info, uint32_t position);

    void MakeArgList(const TaggedArray *argv);
    void MakeArgListWithHole(const TaggedArray *argv, uint32_t length);

    void MakeBoundArgv(const JSThread *thread, const JSHandle<JSBoundFunction> &boundFunc);

    void Iterate(const RootRangeVisitor &v) const;

private:
    DEFAULT_COPY_SEMANTIC(InternalCallParams);
    DEFAULT_MOVE_SEMANTIC(InternalCallParams);

    inline bool IsFixedMode() const
    {
        return !variableMode_;
    }

    inline void EnableFixedModeAndSetLength(uint32_t length)
    {
        variableMode_ = false;
        variableData_.clear();
        variableLength_ = 0;
        fixedLength_ = length;
    }

    inline uint32_t GetFixedLength() const
    {
        return fixedLength_;
    }

    inline uintptr_t GetFixedDataAddress() const
    {
        return ToUintPtr(&fixedData_);
    }

    inline JSTaggedType GetFixedBuffer(uint32_t idx) const
    {
        return fixedData_[idx];
    }

    inline void SetFixedBuffer(uint32_t idx, JSHandle<JSTaggedValue> val)
    {
        fixedData_[idx] = val.GetTaggedType();
    }

    inline void SetFixedBuffer(uint32_t idx, JSTaggedValue val)
    {
        fixedData_[idx] = val.GetRawData();
    }

    inline void SetFixedBuffer(uint32_t idx, JSTaggedType val)
    {
        fixedData_[idx] = val;
    }

    inline void EnableVariableModeAndSetLength(uint32_t length)
    {
        variableMode_ = true;
        fixedLength_ = 0;
        variableLength_ = length;
        variableData_.resize(variableLength_);
    }

    inline uint32_t GetVariableLength() const
    {
        return variableLength_;
    }

    inline uintptr_t GetVariableDataAddress() const
    {
        return ToUintPtr(variableData_.data());
    }

    inline JSTaggedType GetVariableBuffer(uint32_t idx) const
    {
        return variableData_[idx];
    }

    inline void SetVariableBuffer(uint32_t idx, JSHandle<JSTaggedValue> val)
    {
        variableData_[idx] = val.GetTaggedType();
    }

    inline void SetVariableBuffer(uint32_t idx, JSTaggedValue val)
    {
        variableData_[idx] = val.GetRawData();
    }

    inline void SetVariableBuffer(uint32_t idx, JSTaggedType val)
    {
        variableData_[idx] = val;
    }

    inline void InsertVariableBuffer(JSTaggedValue val)
    {
        variableData_.insert(variableData_.begin(), val.GetRawData());
    }

    std::array<JSTaggedType, RESERVE_INTERNAL_CALL_PARAMS_FIXED_LENGTH> fixedData_ {};
    PandaVector<JSTaggedType> variableData_ {};
    uint32_t fixedLength_ {0};
    uint32_t variableLength_ {0};
    bool variableMode_ {false};
};
}  // namespace ark::ecmascript

#endif  // ECMASCRIPT_INTERNAL_CALL_PARAMS_H
